﻿namespace WebPacker.Providers
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Text;
	using System.Threading;
	using System.Web;
	using WebPacker.Compressors;
	using WebPacker.Helpers;
	using WebPacker.Logger;
	using WebPacker.Packs;

	public class FileSystemPackProvider : IPackProvider, IDisposable
	{
		private static readonly ILogger logger = LogFactory.GetLogger();

		private readonly ReaderWriterLockSlim lockSlim;
		private readonly IDictionary<string, FileInfo> packFileCache;

		private string outputPath;
		private string virtualPath;
		private string phisicalPath;
		private bool disposed;

		public FileSystemPackProvider()
		{
			lockSlim = new ReaderWriterLockSlim();
			packFileCache = new Dictionary<string, FileInfo>();
			OutputPath = "~/resources";
			Versioning = true;
		}

		public bool Versioning { get; set; }

		public string OutputPath
		{
			get 
			{ 
				return outputPath; 
			}

			set
			{
				if (value == null)
				{
					throw new ArgumentNullException("value");
				}

				outputPath = value;
				virtualPath = VirtualPathHelper.ToVirtualPath(value);
				phisicalPath = VirtualPathHelper.ToPhisicalPath(value);

				if (!virtualPath.EndsWith("/"))
				{
					virtualPath += "/";
				}
			}
		}

		public string Url
		{
			get { return null; }
		}

		public void Process(HttpContextBase context)
		{
			throw new WebPackerException("Invalid operation.");
		}

		public string GetUrl(ICompressor compressor, IPack pack)
		{
			try
			{
				logger.Debug(() => string.Format("Get url for pack {0}.", pack.ToLogString()));

				if (pack == null)
				{
					throw new ArgumentNullException("pack");
				}

				if (compressor == null)
				{
					throw new ArgumentNullException("compressor");
				}

				FileInfo fileInfo;
				var packKey = GetPackKey(compressor, pack);

				try
				{
					lockSlim.EnterReadLock();
					packFileCache.TryGetValue(packKey, out fileInfo);
				}
				finally
				{
					lockSlim.ExitReadLock();
				}

				var version = pack.Version;

				if (fileInfo == null || fileInfo.Version != version)
				{
					try
					{
						lockSlim.EnterWriteLock();

						if (!packFileCache.TryGetValue(packKey, out fileInfo) || fileInfo.Version != version)
						{
							logger.Info(() => string.Format("Pack {0} is obsolete.", pack.ToLogString()));
							fileInfo = BuildPack(compressor, pack);
							packFileCache[packKey] = fileInfo;
						}
					}
					finally
					{
						lockSlim.ExitWriteLock();
					}
				}

				return fileInfo.Url;
			}
			catch (Exception e)
			{
				logger.Exception(string.Format("Error at get url for pack {0}.", pack.ToLogString()), e);
				throw;
			}
		}

		public void Dispose()
		{
			Dispose(true);
		}

		internal static string GetFileName(bool versioning, ICompressor compressor, IPack pack)
		{
			if (pack == null)
			{
				throw new ArgumentNullException("pack");
			}

			if (compressor == null)
			{
				throw new ArgumentNullException("compressor");
			}

			var builder = new StringBuilder(pack.Name.Length + 10);
			builder.Append(pack.Name);

			if (versioning && !string.IsNullOrEmpty(pack.Version))
			{
				builder.Append('.').Append(pack.Version);
			}

			if (pack.PackType == PackType.JavaScript)
			{
				builder.Append(".js");
			}
			else if (pack.PackType == PackType.Css)
			{
				builder.Append(".css");
			}

			if (!string.IsNullOrEmpty(compressor.Name))
			{
				builder.Append('.').Append(compressor.Name);
			}

			return builder.ToString();
		}

		protected virtual void Dispose(bool disposing)
		{
			if (disposing && !disposed)
			{
				lockSlim.Dispose();
				disposed = true;
			}
		}

		protected virtual string GetFileName(ICompressor compressor, IPack pack)
		{
			return GetFileName(Versioning, compressor, pack);
		}

		private string GetPackKey(ICompressor compressor, IPack pack)
		{
			return GetFileName(false, compressor, pack);
		}

		private string GetFileUrl(string fileName, string version)
		{
			var builder = new StringBuilder(virtualPath.Length + fileName.Length + version.Length + 1);
			
			builder.Append(virtualPath).Append(fileName);

			if (!Versioning)
			{
				builder.Append('?').Append(version);
			}

			return builder.ToString();
		}

		private FileInfo BuildPack(ICompressor compressor, IPack pack)
		{
			var version = pack.Version;

			var fileName = GetFileName(compressor, pack);
			var url = GetFileUrl(fileName, version);

			try
			{
				logger.Info(() => string.Format("Build file {0} for pack {1}.", fileName, pack.ToLogString()));

				var path = VirtualPathHelper.CombinePath(phisicalPath, fileName);
				var content = pack.GetContent();

				using (var stream = File.Create(path, content.Length, FileOptions.WriteThrough))
				{
					compressor.Compress(content, stream);
				}
			}
			catch (Exception e)
			{
				logger.Exception(string.Format("Error at build file {0} for pack {1}.", fileName, pack.ToLogString()), e);
				throw;
			}

			return new FileInfo(url, pack.Version);
		}

		private class FileInfo
		{
			private readonly string url;
			private readonly string version;

			public FileInfo(string url, string version)
			{
				this.url = url;
				this.version = version;
			}

			public string Url
			{
				get { return url; }
			}

			public string Version
			{
				get { return version; }
			}
		}
	}
}